﻿//-----------------------------------------------------------------------
// <copyright>
//     Copyright (c) Artur Mustafin. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Expressions.Compiler
{
    using System.Collections.Generic;
    using System.Text;
    using Internal;

    /// <summary>
    /// 
    /// </summary>
    public sealed class Terminal<T> : Entity<T>
    {
        private static Terminal<T> _null = new Terminal<T>();

        private readonly T[] _value;
        private readonly string _name;
        private readonly bool _result;

        private static readonly Dictionary<string, Terminal<T>> _terminals;

        /// <summary>
        /// Initializes a new instance of the <see cref="Terminal"/> class.
        /// </summary>
        /// <param name="name">The input name</param>
        /// <param name="value">The value.</param>
        /// <param name="optional">if set to <c>true</c> [optional].</param>
        /// <param name="result">if set to <c>true</c> [result].</param>
        private Terminal(string name, T[] value, bool optional, bool result)
            : base(optional)
        {
            _name = name;
            _value = value;
            _result = result;
            _terminals.Add(name, this);
        }

        /// <summary>
        /// Implements the operator !.
        /// </summary>
        /// <param name="terminal">The input terminal.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens<T> operator !(Terminal<T> terminal)
        {
            Tokens<T> stream = null;
            if (terminal != (Terminal<T>)null)
            {
                stream = GetTerminal(string.Format("!{0}", terminal._name), terminal._value, terminal.Optional, !terminal._result);
            }
            return stream;
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The terminal a.</param>
        /// <param name="b">The terminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens<T> operator +(Terminal<T> a, Terminal<T> b)
        {
            Tokens<T> stream1 = a;
            Tokens<T> stream2 = b;
            return stream1 + stream2;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Terminal"/> class.
        /// </summary>
        private Terminal()
        {
            _name = "|";
            _value = new T[] { default(T) };
        }

        /// <summary>
        /// Initializes the <see cref="Terminal"/> class.
        /// </summary>
        static Terminal()
        {
            _terminals = new Dictionary<string, Terminal<T>>();
            _terminals.Add(Terminal<T>.Default.ToString(), _null);
            _terminals.Add(string.Empty, _null);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="Expressions.Compiler.Terminal"/> to <see cref="System.Value"/>.
        /// </summary>
        /// <param name="terminal">The terminal.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Terminal<T>(T[] value)
        {
            return GetTerminal(GetString(value), value, false, false);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="Expressions.Compiler.Terminal"/> to <see cref="System.Value"/>.
        /// </summary>
        /// <param name="terminal">The terminal.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Terminal<T>(T value)
        {
            return GetTerminal(value.ToString(), new T[] { value }, false, false);
        }

        private static string GetString(IEnumerable<T> values)
        {
            if (values != null)
            {
                StringBuilder sb = new StringBuilder();
                foreach (T value in values)
                {
                    sb.AppendFormat("{0}", value);
                }
                return sb.ToString();
            }
            return string.Empty;
        }

        public static Tokens<T> operator +(T ch, Terminal<T> terminal)
        {
            Tokens<T> stream1 = ch;
            Tokens<T> stream2 = terminal;
            return stream1 + stream2;
        }

        public static Tokens<T> operator +(T[] ch, Terminal<T> terminal)
        {
            Tokens<T> stream1 = ch;
            Tokens<T> stream2 = terminal;
            return stream1 + stream2;
        }

        /// <summary>
        /// Gets the terminal.
        /// </summary>
        /// <param name="ch">The ch.</param>
        /// <param name="name">The key.</param>
        /// <returns>Returns terminal</returns>
        private static Terminal<T> GetTerminal(string name, T[] value, bool optional, bool result)
        {
            if (name == null)
            {
                return _null;
            }
            if (!_terminals.ContainsKey(name))
            {
                return new Terminal<T>(name, value, optional, result);
            }
            Terminal<T> terminal = _terminals[name];
            if (terminal.Optional != optional)
            {
                name = string.Format("({0})", name);
                if (!_terminals.ContainsKey(name))
                {
                    return new Terminal<T>(name, value, optional, result);
                }
                return _terminals[name];
            }
            return terminal;
        }

        /// <summary>
        /// Gets the <see cref="Expressions.Compiler.Tokens"/> with the specified optional.
        /// </summary>
        /// <value></value>
        public Terminal<T> this[bool optional]
        {
            get
            {
                return GetTerminal(_name, _value, optional, _result);
            }
        }

        /// <summary>
        /// Parses the specified text.
        /// </summary>
        /// <param name="parser">The input text</param>
        /// <returns>A boolean value</returns>
        public override bool Parse(Parser<T> parser)
        {
            Position<T> end;
            if (Parse(parser, _result, out end))
            {
                if (!Optional)
                {
                    Position<T> start = parser.Current;
                    parser.Read(end);

#if TRACE
                    parser.Parse(this, Name, start, end);
#else
                    parser.Parse(this, start, end);
#endif
                }
                return true;
            }
            return false;
        }

        internal bool Read(Position<T> stream)
        {
            bool result = Result;
            foreach (T value in _value)
            {
                if (!object.Equals(value, stream.Value))
                {
                    return result;
                }
                if (stream != null)
                {
                    stream = stream.Read();
                }
            }
            return !result;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <value>The value.</value>
        internal IEnumerable<T> Value
        {
            get
            {
                return _value;
            }
        }

#if TRACE

        private const char _ch = '|';

        private string GetName()
        {
            if (_value == null || _value.Length == 0 || object.Equals(_value[0], Default)) // == Terminal<T>.Null.ToString() || string.IsNullOrEmpty(_value) || string.IsNullOrWhiteSpace(_value))
            {
                if (Optional)
                {
                    return string.Format("({0})", _ch);
                }
                return _ch.ToString();
            }
            return _name;
        }

        public override string ToString()
        {
            return GetName();
        }

        internal override string Name
        {
            get
            {
                return GetName();
            }
        }
#endif

        /// <summary>
        /// 
        /// </summary>
        public static T Default
        {
            get
            {
                return _null._value[0];
            }
        }

        /// <summary>
        /// Reads the specified terminal.
        /// </summary>
        /// <param name="terminal">The terminal.</param>
        /// <param name="text">The input text</param>
        /// <param name="equals">if set to <c>true</c> [equals].</param>
        /// <param name="current">The current.</param>
        /// <returns>A boolean value</returns>
        private bool Parse(Parser<T> text, bool equals, out Position<T> current)
        {
            Position<T> next = text.Current;
            int count = _value.Length;
            IEnumerator<T> enumerator = ((IEnumerable<T>)_value).GetEnumerator();
            current = next;
            while (current != null && enumerator.MoveNext() && count > 0)
            {
                if (!equals)
                {
                    if (!object.Equals(enumerator.Current, current.Value))
                    {
                        return count == 0;
                    }
                }
                else
                {
                    if (object.Equals(enumerator.Current, current.Value))
                    {
                        return count == 0;
                    }
                }
                current = current.Read();
                --count;
            }
            return count == 0;
        }

#if !RECURSION
        /// <summary>
        /// Gets a value indicating whether this <see cref="Terminal"/> is equals by result.
        /// </summary>
        /// <value><c>true</c> if result; otherwise, <c>false</c>.</value>
        internal bool Result
        {
            get
            {
                return _result;
            }
        }
#endif
    }

    /// <summary>
    /// 
    /// </summary>
    public sealed class Terminal : Entity
    {
        /// <summary>
        /// 
        /// </summary>
        private static Terminal _null = new Terminal();

        /// <summary>
        /// 
        /// </summary>
        private readonly string _value;

        /// <summary>
        /// 
        /// </summary>
        private readonly string _name;

        /// <summary>
        /// 
        /// </summary>
        private readonly bool _result;

        private static readonly Dictionary<string, Terminal> _terminals;

        /// <summary>
        /// Initializes a new instance of the <see cref="Terminal"/> class.
        /// </summary>
        /// <param name="name">The input name</param>
        /// <param name="value">The value.</param>
        /// <param name="optional">if set to <c>true</c> [optional].</param>
        /// <param name="result">if set to <c>true</c> [result].</param>
        private Terminal(string name, string value, bool optional, bool result)
            : base(optional)
        {
            _name = name;
            _value = value;
            _result = result;
            _terminals.Add(name, this);
        }

        /// <summary>
        /// Implements the operator !.
        /// </summary>
        /// <param name="terminal">The input terminal.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator !(Terminal terminal)
        {
            Tokens stream = null;
            if (terminal != (Terminal)null)
            {
                stream = GetTerminal(string.Format("!{0}", terminal._name), terminal._value, terminal.Optional, !terminal._result);
            }
            return stream;
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The terminal a.</param>
        /// <param name="b">The terminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator +(Terminal a, Terminal b)
        {
            Tokens stream1 = a;
            Tokens stream2 = b;
            return stream1 + stream2;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Terminal"/> class.
        /// </summary>
        private Terminal()
        {
            _name = "|";
            _value = Terminal.Null.ToString();
        }

        /// <summary>
        /// Initializes the <see cref="Terminal"/> class.
        /// </summary>
        static Terminal()
        {
            _terminals = new Dictionary<string, Terminal>();
            _terminals.Add(Terminal.Null.ToString(), _null);
            _terminals.Add(string.Empty, _null);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="Expressions.Compiler.Terminal"/> to <see cref="System.Char"/>.
        /// </summary>
        /// <param name="terminal">The terminal.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Terminal(char ch)
        {
            return GetTerminal(ch.ToString(), ch.ToString(), false, false);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.String"/> to <see cref="Expressions.Compiler.Terminal"/>.
        /// </summary>
        /// <param name="termainal">The termainal.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Terminal(string str)
        {
            return GetTerminal(str, str, false, false);
        }

        public static Tokens operator +(char ch, Terminal terminal)
        {
            Tokens stream1 = ch;
            Tokens stream2 = terminal;
            return stream1 + stream2;
        }

        public static Tokens operator +(string ch, Terminal terminal)
        {
            Tokens stream1 = ch;
            Tokens stream2 = terminal;
            return stream1 + stream2;
        }

        /// <summary>
        /// Gets the terminal.
        /// </summary>
        /// <param name="ch">The ch.</param>
        /// <param name="name">The key.</param>
        /// <returns>Returns terminal</returns>
        private static Terminal GetTerminal(string name, string value, bool optional, bool result)
        {
            if (name == null)
            {
                return _null;
            }
            if (!_terminals.ContainsKey(name))
            {
                return new Terminal(name, value, optional, result);
            }
            Terminal terminal = _terminals[name];
            if (terminal.Optional != optional)
            {
                name = string.Format("({0})", name);
                if (!_terminals.ContainsKey(name))
                {
                    return new Terminal(name, value, optional, result);
                }
                return _terminals[name];
            }
            return terminal;
        }

        /// <summary>
        /// Gets the <see cref="Expressions.Compiler.Tokens"/> with the specified optional.
        /// </summary>
        /// <value></value>
        public Terminal this[bool optional]
        {
            get
            {
                return GetTerminal(_name, _value, optional, _result);
            }
        }

        /// <summary>
        /// Parses the specified text.
        /// </summary>
        /// <param name="parser">The input text</param>
        /// <returns>A boolean value</returns>
        public override bool Parse(Parser parser)
        {
            Position end;
            if (Parse(parser, _result, out end))
            {
                if (!Optional)
                {
                    Position start = parser.Current;
                    parser.Read(end);
#if TRACE
                    parser.Parse(this, Name, start, end);
#else
                    parser.Parse(this, start, end);
#endif
                }
                return true;
            }
            return false;
        }

        internal bool Read(Position stream)
        {
            bool result = Result;
            foreach (char ch in Value)
            {
                if (ch != stream.Char)
                {
                    return result;
                }
                if (stream != null)
                {
                    stream = stream.Read();
                }
            }
            return !result;
        }

        private const char _ch = '|';

        /// <summary>
        /// 
        /// </summary>
        public const char Null = (char)65535;

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <value>The value.</value>
        internal string Value
        {
            get
            {
                return _value;
            }
        }

        /// <summary>
        /// Reads the specified terminal.
        /// </summary>
        /// <param name="terminal">The terminal.</param>
        /// <param name="text">The input text</param>
        /// <param name="equals">if set to <c>true</c> [equals].</param>
        /// <param name="current">The current.</param>
        /// <returns>A boolean value</returns>
        private bool Parse(Parser text, bool equals, out Position current)
        {
            Position next = text.Current;
            int count = _value.Length;
            IEnumerator<char> enumerator = _value.GetEnumerator();
            current = next;
            while (current != null && enumerator.MoveNext() && count > 0)
            {
                if (!equals)
                {
                    if (enumerator.Current != current.Char)
                    {
                        return count == 0;
                    }
                }
                else
                {
                    if (enumerator.Current == current.Char)
                    {
                        return count == 0;
                    }
                }
                current = current.Read();
                --count;
            }
            return count == 0;
        }

#if TRACE
        internal override string Name
        {
            get
            {
                return GetName();
            }
        }
        
        private string GetName()
        {
            if (_value == null || _value.Length == 0 || _value == Terminal.Null.ToString())
            {
                if (Optional)
                {
                    return string.Format("({0})", _ch);
                }
                return _ch.ToString();
            }
            return _name;
        }

        public override string ToString()
        {
            return GetName();
        }
#endif

#if !RECURSION
        /// <summary>
        /// Gets a value indicating whether this <see cref="Terminal"/> is equals by result.
        /// </summary>
        /// <value><c>true</c> if result; otherwise, <c>false</c>.</value>
        internal bool Result
        {
            get
            {
                return _result;
            }
        }
#endif
    }
}
